-
-
Notifications
You must be signed in to change notification settings - Fork 956
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make hooks better 🦄 #523
Make hooks better 🦄 #523
Conversation
// @jstewmon |
); | ||
} | ||
for (const [hookEvent, hooks] of Object.entries(options.hooks)) { | ||
if (is.array(hooks)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails to validate the known hooks if something other than an array is provided, which will result in an error upstream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, thanks - I got tripped up reviewing the diff.
} | ||
for (const [hookEvent, hooks] of Object.entries(options.hooks)) { | ||
if (is.array(hooks)) { | ||
for (const [index, hook] of Object.entries(hooks)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not equivalent to the current implementation. Consider these examples:
const a = [1, 2];
a.foo = 'bar';
Object.entries(a); // [ [ '0', 1 ], [ '1', 2 ], [ 'foo', 1 ] ]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a bug. It's a feature!
@@ -132,7 +132,7 @@ test('throws TypeError when known `hooks` array item is not a function', async t | |||
}); | |||
|
|||
test('allows extra keys in `hooks`', async t => { | |||
await t.notThrows(() => got(`${s.url}/test`, {hooks: {extra: {}}})); | |||
await t.notThrows(() => got(`${s.url}/test`, {hooks: {extra: []}})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of this test is to show that extra keys are not required to pass validation b/c they should be ignored. The test title could be more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would someone assign non-hook object to hooks? What's the use case? What big problem does this solve? Sorry, but I don't see that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It allows for encapsulation. Here's a contrived example:
class GotHooks {
constructor () {
this.message = 'Encapsulated Got Hooks';
this.beforeRequest = [
() => this.runRook()
];
}
async runRook() {
console.log(this.message);
}
}
If got validates only the properties it is concerned with, then everything is fine. If got extraneously demands that the object only have properties that correspond to known hooks, then the user has to jump through some hoops to create an object that accomplishes the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the detailed response, I really appreciate that! 👍
@@ -150,7 +157,9 @@ module.exports = (options = {}) => { | |||
|
|||
const socket = req.connection; | |||
if (socket) { | |||
const onSocketConnect = () => { | |||
const onSocketConnect = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the origin of this conditional block are, but I think the right way to handle this event is to remove the if block and make this:
req.on('socket', () => { ... });
https://nodejs.org/dist/latest-v8.x/docs/api/http.html#http_event_socket
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, since onSocketConnect
and onAbort
should be attached to the request, I don't think these are sensible hooks for got
to configure directly. If you want to configure these for a got instance, you really just need to configure a listener for request
and attach the request event listeners in the request listener.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the origin of this conditional block are, but I think the right way to handle this event is to remove the if block
Actually, since onSocketConnect and onAbort should be attached to the request, I don't think these are sensible hooks for got to configure directly. If you want to configure these for a got instance, you really just need to configure a listener for request and attach the request event listeners in the request listener.
Why would you do the same thing twice? Oh wait. I do that twice. I'll change that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reference to #429. Maybe I'm missing something, but I don't think that was the right fix b/c if req.connection
is just testing a race condition. Shouldn't it be:
req.once('socket', (sock) => {
sock.once('connect', conn => {
// ...
}
};
I can see the convenience of low-friction hooks. Maybe there's a way to make the configuration a sort of DSL that got can use to wire these up. Something like this:
{
events: {
request: {
['on|once']: {
socket: {
null: [(sock) => { console.log('request.on(socket)') }],
connect: [(conn) => { console.log('socket.on(connect)') }]
}
},
}
}
}
With the above, the configuration can be traversed and the listeners can be subscribed without having to add handling code to got for every event.
I used null
as a sentinel value to mean the subscriber for the event as opposed to a child subscription. Something else (symbol?) would work also.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm missing something, but I don't think that was the right fix b/c if req.connection is just testing a race condition. Shouldn't it be:
I don't know. Can you make another issue for that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see the convenience of low-friction hooks.
Great! We'll discuss this later. I was gonna send a PR with rewrited hooks from scratch but I decided current implementation is better. :)
I really like being able to configure event listeners on a per-instance basis - it's really useful for being able to add instrumentation to a client without having to litter every request site with the event listeners. I have a few questions regarding the implementation:
|
You're right.
I don't understand. EE listeners aren't awaited.
It's frozen to prevent unwanted changes to the instance. But yeah, some people might want to keep them frozen and some people might want to not. My proposal: to enable freezing people will need to set |
You're using |
Can we agree that the options should either be frozen or not? If so, then I think you can cut down this PR by reverting the changes to how the configuration is setup and normalized, right? That will allow us to focus on the interface for the event listeners. |
I'm closing this due to a GitHub crash. This PR doesn't see new commits. I'll create another PR when I'm done with fixing things. Big thanks to @jstewmon for letting me know what I've done wrong! 🙌 |
It's fixed in the upcoming PR. |
My answer: |
got.hooks = got.defaults.options.hooks
(all defaults are deep frozen excepthooks
)(before: if you hadn't specified
hooks
in the defaults,got.hooks
would beundefined
)onSocketConnect
,onAbort